How can I password protect every page in NextJS and Supabase, using the Supabase default auth helpers and UI? - authentication

I'm trying to add user authentication to every page in my NextJS project (pages, not app.) This tutorial was very helpful (and is exactly what I want to do) - https://alexsidorenko.com/blog/next-js-protected-routes/ - but I'm having trouble integrating Supabase's default auth UI and capabilities into that model (https://supabase.com/docs/guides/auth/auth-helpers/nextjs).
My basic goal is to move authentication branching into _app.tsx, rather than on each page:
// _app.tsx
import { useEffect, useState } from "react";
import { createBrowserSupabaseClient } from '#supabase/auth-helpers-nextjs'
import { SessionContextProvider, useUser, useSession, useSupabaseClient, Session } from '#supabase/auth-helpers-react'
import { Auth, ThemeSupa } from '#supabase/auth-ui-react'
import { AppProps } from 'next/app'
import { UserContext } from "#components/user"
function MyApp({Component, pageProps}: AppProps<{ initialSession: Session }>) {
const [supabase] = useState(() => createBrowserSupabaseClient())
const session = useSession()
const user = useUser()
console.log("session:" + session);
console.log("user:" + user);
useEffect(() => {
if (
pageProps.protected
) {
return <Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
}
}, [])
return (
<SessionContextProvider supabaseClient={supabase} session={session} initialSession={pageProps.initialSession}>
<Component {...pageProps} />
</SessionContextProvider>
)
}
export default MyApp
A page I want to protect (for example, the index page) looks like this:
// index.tsx
import Account from "#components/account";
const Home = () => {
return (
<div>
<Account session={session} />
</div>
)
}
export async function getStaticProps(context) {
return {
props: {
protected: true,
},
}
}
export default Home
And then the Account component that's included on the index page is the Supabase out of the box profile panel, although it could be any content:
// #components/account.tsx
import { useState, useEffect } from 'react'
import { useUser, useSupabaseClient, Session } from '#supabase/auth-helpers-react'
import { Database } from '#utils/database.types'
type Profiles = Database['public']['Tables']['profiles']['Row']
export default function Account({ session }: { session: Session }) {
const supabase = useSupabaseClient<Database>()
const user = useUser()
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState<Profiles['username']>(null)
useEffect(() => {
getProfile()
}, [session])
async function getProfile() {
try {
setLoading(true)
if (!user) throw new Error('No user')
let { data, error, status } = await supabase
.from('profiles')
.select(`username`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
}
} catch (error) {
alert('Error loading user data!')
console.log(error)
} finally {
setLoading(false)
}
}
async function updateProfile({
username,
}: {
username: Profiles['username']
}) {
try {
setLoading(true)
if (!user) throw new Error('No user')
const updates = {
id: user.id,
username,
updated_at: new Date().toISOString(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) throw error
alert('Profile updated!')
} catch (error) {
alert('Error updating the data!')
console.log(error)
} finally {
setLoading(false)
}
}
return (
<div>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Username</label>
<input id="username" type="text" value={username || ''} onChange={(e) => setUsername(e.target.value)} />
</div>
<div>
<button onClick={() => updateProfile({ username })} disabled={loading} >
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
</div>
)
}
I think I have a fundamental misunderstanding of the relationship between protected routes and Supabase's use of session and user.
Any help would be very much appreciated.

I'd recommend using Next.js middleware for this: https://supabase.com/docs/guides/auth/auth-helpers/nextjs#auth-with-nextjs-middleware
import { createMiddlewareSupabaseClient } from '#supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(req: NextRequest) {
// We need to create a response and hand it to the supabase client to be able to modify the response headers.
const res = NextResponse.next()
// Create authenticated Supabase Client.
const supabase = createMiddlewareSupabaseClient({ req, res })
// Check if we have a session
const {
data: { session },
} = await supabase.auth.getSession()
// Check auth condition
if (session?.user.email?.endsWith('#gmail.com')) {
// Authentication successful, forward request to protected route.
return res
}
// Auth condition not met, redirect to home page.
const redirectUrl = req.nextUrl.clone()
redirectUrl.pathname = '/'
redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname)
return NextResponse.redirect(redirectUrl)
}
export const config = {
matcher: '/middleware-protected/:path*',
}

Related

How to access request data in layouts and pages in Qwik City

I'm creating an application Qwik and Qwik City.
I want to show something in my layout, only if it's the home page.
This is my layout.jsx:
const Layout = component$((props) => {
// console.log(props); // I don't see anything related to request
const isHomePage = true; // how can I know I'm inside the home page?
return <>
{isHomePage && <TopBar />}
</>
})
import { component$, Slot } from "#builder.io/qwik";
import { loader$, useLocation } from "#builder.io/qwik-city";
export const useUser = loader$(async ({ fail }) => {
try {
const res = await fetch("https://api.github.com/users/harshmangalam");
const user = await res.json();
return {
user,
};
} catch (error) {
return fail(500, {
error: "Something went wrong",
});
}
});
export default component$(() => {
const user = useUser();
const {
url: { pathname },
} = useLocation();
const isHomePage = pathname === "/";
return (
<div>
{isHomePage && <pre>{JSON.stringify(user.value, null, 4)}</pre>}
<Slot />
</div>
);
});

Compare Contract Settings Goerli on Etherscan

I recently took over a project from another developer who has been uncooperative in providing assistance. The project includes a contract deployed on the Rinkeby network and a decentralized exchange (DEX) developed for it. Users were able to test the DEX using our own platform, with liquidity added via the Uniswap interface. The contract includes the Uniswap V2Router contract to fetch liquidity and use it on our DEX. I have completed all necessary web3 setup, including changing the RPC port to 5 and adding the correct URL.
No error so far on my console.
SITE FOR REFERENCE: https://doxastaking.netlify.app/, It is deployed on GOERLI ETHER.
Recently, I migrated the project to the Goerli network. However, since the migration, I have not been able to purchase tokens using our DEX. Transactions go through, but the equivalent tokens are not assigned to my wallet. It is unclear to me whether the issue is with the contract or the web3 setup.
The web3 swap code:
import React, { Component } from "react";
import eth from "../../assets/eth.png";
import transfer from "../../assets/transfer.png";
import buyLoader from "../../assets/doxa-ico-loader.gif";
import { connect } from "react-redux";
import { connectWallet } from '../../redux/WalletAction';
import miniLogo from "../../assets/logo.png";
import "./scss/bs.css";
class Buydoxa extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: '0',
doxaValue: 0
}
}
componentDidMount() {
const { web3Modal } = this.props.wallet
if (web3Modal.cachedProvider) {
this.props.connectWallet();
}
}
connectToWallet = async () => {
await this.props.connectWallet();
}
buyToken = async () => {
const queryParams = new URLSearchParams(window.location.search);
const id = queryParams.get('email');
let inputValue = parseFloat(this.state.inputValue);
if (inputValue >= 0.00001 && inputValue <= 10) {
const { web3, doxa, wallet, address } = this.props.wallet;
const value = this.state.inputValue.toString();
const buyValue = web3.utils.toWei(value, 'ether');
const tokenPrice = web3.utils.toWei('0.00001', 'ether');
const totalTokens = (web3.utils.toBN(buyValue).div(web3.utils.toBN(tokenPrice))).toString();
try {
this.setState({ loading: true })
const res = await wallet.methods.swapEthToDoxa(id).send({ from: address, value: buyValue });
this.setState({ loading: false })
} catch (err) {
this.setState({ loading: false })
if (err.message) {
alert(err.message)
} else {
alert("Something went wrong!")
}
}
} else {
alert("ETH should be between 0.00001 and 10");
return
}
}
updateInputValue = async (e) => {
let totalTokens;
if (e.target.value != '') {
totalTokens = parseFloat(e.target.value) / 0.00001;
}
this.setState({
inputValue: e.target.value,
doxaValue: totalTokens
});
}
render() {
return (
<div className="bs-container h-100">
<div className="bs-main">
<h2>swap your crypto</h2>
<div className="bs-input">
<div className="inpt-cont center mb-3">
<label>Enter ETH</label>
<input type="number" value={this.state.inputValue} onChange={e => this.updateInputValue(e)} />
</div>
<div className="img-cont">
<img src={eth} alt="eth" />
<p>ETH</p>
</div>
</div>
{/* image */}
<img src={transfer} className="transfer" alt="transfer" />
<div className="bs-input">
<div className="inpt-cont center">
<p>{this.state.doxaValue}</p>
</div>
<div className="img-cont">
<img src={miniLogo} alt="miniLogo" />
<p>DOXAZO</p>
</div>
</div>
{/* btn */}
<button className="bs-btn" disabled={this.state.loading} onClick={() => this.props.wallet.connected ? this.buyToken() : this.connectToWallet()}>{this.props.wallet.connected ? this.state.loading ?
<span>Processing <img src={buyLoader}></img></span>
: 'Buy' : 'PROCEED TO SWAP'}</button>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
wallet: state.walletConnect
});
export default connect(mapStateToProps, { connectWallet })(Buydoxa);
My Wallet Action.js Codes:
// constants
import Web3 from "web3";
import Web3Modal from "web3modal";
import WalletConnectProvider from "#walletconnect/web3-provider";
import contract from "../contracts/staking.json";
import tokenContract from "../contracts/token.json";
import walletContract from "../contracts/wallet.json";
import store from './store';
const connectRequest = () => {
return {
type: "CONNECTION_REQUEST",
};
};
export const disconnectRequest = () => {
return {
type: "DISCONNECT"
};
}
export const connectSuccess = (payload) => {
return {
type: "CONNECTION_SUCCESS",
payload: payload,
};
};
const connectFailed = (payload) => {
return {
type: "CONNECTION_FAILED",
payload: payload,
};
};
export const updateAccountRequest = (payload) => {
return {
type: "UPDATE_ADDRESS",
payload: payload,
};
};
const getProviderOptions = () => {
const providerOptions = {
walletconnect: {
package: WalletConnectProvider,
options: {
rpc: {
5: "https://goerli.infura.io/v3/ea95b0776037479abf7a62fc14b55188",
1: "https://mainnet.infura.io/v3/ea95b0776037479abf7a62fc14b55188"
}
}
},
}
return providerOptions;
}
export const connectWallet = () => {
return async(dispatch) => {
dispatch(connectRequest());
try {
const web3Modal = new Web3Modal({
cacheProvider: true,
providerOptions: getProviderOptions() // required
});
const provider = await web3Modal.connect();
const stakingContractAddress = process.env.REACT_APP_DOXACONTRACT_ADDRESS;
const internalWalletAddress = process.env.REACT_APP_WALLET_ADDRESS;
const TokencontractAddress = process.env.REACT_APP_TOKEN_ADDRESS;
await subscribeProvider(provider, dispatch);
const web3 = new Web3(provider);
web3.eth.extend({
methods: [
{
name: "chainId",
call: "eth_chainId",
outputFormatter: web3.utils.hexToNumber
}
]
});
const accounts = await web3.eth.getAccounts();
const address = accounts[0];
const instance = new web3.eth.Contract(
contract,
stakingContractAddress
);
const tokenInstance = new web3.eth.Contract(
tokenContract,
TokencontractAddress
)
const walletInstance = new web3.eth.Contract(
walletContract,
internalWalletAddress
)
if(window.ethereum && window.ethereum.networkVersion !== '5') {
await addNetwork(5);
}
dispatch(
connectSuccess({
address,
web3,
staking: instance,
token: tokenInstance,
wallet: walletInstance,
provider,
connected: true,
web3Modal
})
);
} catch (e) {
dispatch(connectFailed(e));
}
}
}
export const disconnect = () => {
return async(dispatch)=> {
const { web3Modal } = store.getState().walletConnect;
console.log(web3Modal);
web3Modal.clearCachedProvider();
dispatch(disconnectRequest());
}
}
const subscribeProvider = async(provider) => {
if (!provider.on) {
return;
}
provider.on('connect', async(id) => {
console.log(id);
});
provider.on("networkChanged", async (networkId) => {
if(networkId !== '5') {
console.log(networkId);
await store.dispatch(connectFailed('Please switch to Ethereum mainnet'));
addNetwork(5);
} else {
store.dispatch(connectWallet());
}
});
}
export async function addNetwork(id) {
let networkData;
switch (id) {
//bsctestnet
case 5:
networkData = [
{
chainId: "0x4",
},
];
break;
//bscmainet
case 1:
networkData = [
{
chainId: "0x1",
},
];
break;
default:
break;
}
return window.ethereum.request({
method: "wallet_switchEthereumChain",
params: networkData,
});
}
(() => {
if(window.ethereum) {
window.ethereum.on('networkChanged', async function(networkId){
console.log('network change', networkId);
if(networkId !== '5') {
console.log(networkId);
await store.dispatch(connectFailed('Please switch to Binance mainnet'));
addNetwork(5);
} else {
store.dispatch(connectWallet());
}
});
}
})();
The following are the contract and wallet addresses for my Goerli deployments:
Token Address: https://goerli.etherscan.io/address/0x0f0283E1aC1f465cE2076a1F57EA0f1BAb4DDC21
Wallet Address Proxy: https://goerli.etherscan.io/address/0xdF6046711651AEC0d686F12Ed0039d5aC45517f3
The following are the contract and wallet addresses for the previous developer's Rinkeby deployments:
Token deployed by other developer on Rinkeby network: https://rinkeby.etherscan.io/address/0xD99b4BB049a6Dd490901CDfa33F15C4fAc097EF0
The wallet proxy deployed on Rinkeby: https://rinkeby.etherscan.io/address/0x5309E16fc58Dc900a08d92BE6559758D692f39Bb

Button takes 2 clicks before store update Vue and Pinia

I have been messing around with Vue and trying to learn it. On the first click of the button in LoginForm.vue token and user_data are both null. On the second click it finally gets updated. How can I get the live reactive state of the variables?
I am new to Vue so if there are better common practices please let me know.
store/login.js
import { defineStore } from 'pinia'
import axios from 'axios'
export const useUsers = defineStore('users', {
state: () => ({
token: null,
user_data: null
}),
actions: {
async loginUser(data) {
try {
let response = await axios.post("users/login", data)
// Object.assign(this.token, response.data.token)
this.token = response.data.token
axios.defaults.headers.common['user-token'] = this.token
} catch (error) {
return error
}
},
async logout() {
// Object.assign(this.token, null)
// Object.assign(this.user_data, null)
this.token = null
this.user_data = null
// localStorage.removeItem('user');
delete axios.defaults.headers.common['user-token']
},
async login_and_get_user_data(data) {
axios.post("users/login", data).then(response => {
this.token = response.data.token
axios.defaults.headers.common['user-token'] = this.token
axios.get("users/user").then(response2 => {
this.user_data = response2.data.user
})
})
},
async get_user_data() {
console.log(JSON.parse(localStorage.getItem('user'))['token'])
axios.defaults.headers.common['user-token'] = JSON.parse(localStorage.getItem('user'))['token']
let response = await axios.get("users/user")
// Object.assign(this.user_data, response.data.user)
this.user_data = response.data.user
}
}
})
components/LoginForm.vue
<script>
import { useUsers } from '#/stores/login'
import { mapActions } from 'pinia'
import { storeToRefs } from 'pinia'
import { isProxy, toRaw } from 'vue';
export default {
setup() {
const store = useUsers()
store.$subscribe((mutation, state) => {
localStorage.setItem('user', JSON.stringify(state))
})
},
data() {
return {
email: "",
password: ""
}
},
methods: {
...mapActions(useUsers, ['loginUser']),
...mapActions(useUsers, ['get_user_data']),
...mapActions(useUsers, ['logout']),
on_click() {
var data = new FormData();
data.append('email', this.email);
data.append('password', this.password);
const store = useUsers()
this.loginUser(data)
this.get_user_data()
const { token } = storeToRefs(store)
const { user_data } = storeToRefs(store)
console.log(token.value)
console.log(toRaw(user_data.value))
},
logout_click() {
this.logout().then(
console.log(JSON.parse(localStorage.getItem('user')))
)
}
}
}
</script>
<template>
<input type="email" v-model="email" placeholder="youremail#mail.com">
<br>
<input type="password" v-model="password">
<br>
<button #click="on_click">Submit</button>
<br>
<button #click="logout_click">Logout</button>
</template>
Your method on_click is calling async methods like loginUser or get_user_data without waiting them to be finished.
So by the time your are logging console.log(token.value) your http request is probably not finished yet and your token is still null.
You need to await the methods that are doing those requests.
async on_click() {
var data = new FormData();
data.append('email', this.email);
data.append('password', this.password);
const store = useUsers()
await this.loginUser(data)
await this.get_user_data()
const { token } = storeToRefs(store)
const { user_data } = storeToRefs(store)
console.log(token.value)
console.log(toRaw(user_data.value))
},
Keep in mind that you will probably need to display a loader to give the user a feedback because the on_click is now asynchronous and take a bit more time

BotFramework-WebChat v4: post activity to direct line from the UI to the bot

My WebChat code is based on the React minimizable-web-chat v4.
I want to send a message to the bot when user click the location button.
handleLocationButtonClick function is called and it sends latitude and longitude to the bot.
This is my code:
import React from 'react';
import { createStore, createStyleSet } from 'botframework-webchat';
import WebChat from './WebChat';
import './fabric-icons-inline.css';
import './MinimizableWebChat.css';
export default class extends React.Component{
constructor(props) {
super(props);
this.handleFetchToken = this.handleFetchToken.bind(this);
this.handleMaximizeButtonClick = this.handleMaximizeButtonClick.bind(this);
this.handleMinimizeButtonClick = this.handleMinimizeButtonClick.bind(this);
this.handleSwitchButtonClick = this.handleSwitchButtonClick.bind(this);
this.handleLocationButtonClick = this.handleLocationButtonClick.bind(this);
const store = createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
}
});
}
else if(action.type === 'DIRECT_LINE/INCOMING_ACTIVITY'){
if (action.payload.activity.name === 'locationRequest') {
this.setState(() => ({
locationRequested: true
}));
}
}
return next(action);
});
this.state = {
minimized: true,
newMessage: false,
locationRequested:false,
side: 'right',
store,
styleSet: createStyleSet({
backgroundColor: 'Transparent'
}),
token: 'token'
};
}
async handleFetchToken() {
if (!this.state.token) {
const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
const { token } = await res.json();
this.setState(() => ({ token }));
}
}
handleMaximizeButtonClick() {
this.setState(() => ({
minimized: false,
newMessage: false
}));
}
handleMinimizeButtonClick() {
this.setState(() => ({
minimized: true,
newMessage: false
}));
}
handleSwitchButtonClick() {
this.setState(({ side }) => ({
side: side === 'left' ? 'right' : 'left'
}));
}
handleLocationButtonClick(){
var x = document.getElementById("display");
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
this.setState(() => ({
locationRequested: false
}));
}
else
{
x.innerHTML = "Geolocation API is not supported by this browser.";
}
function showPosition(position) {
x.innerHTML = "Latitude: " + position.coords.latitude + "<br>Longitude: " + position.coords.longitude;
this.store.dispatch({
type: 'WEB_CHAT/SEND_MESSAGE',
payload: { text: 'latitude:'+position.coords.latitude+'longitude:'+position.coords.longitude }
});
}
}
render() {
const { state: {
minimized,
newMessage,
locationRequested,
side,
store,
styleSet,
token
} } = this;
return (
<div className="minimizable-web-chat">
{
minimized ?
<button
className="maximize"
onClick={ this.handleMaximizeButtonClick }
>
<span className={ token ? 'ms-Icon ms-Icon--MessageFill' : 'ms-Icon ms-Icon--Message' } />
{
newMessage &&
<span className="ms-Icon ms-Icon--CircleShapeSolid red-dot" />
}
</button>
:
<div
className={ side === 'left' ? 'chat-box left' : 'chat-box right' }
>
<header>
<div className="filler" />
<button
className="switch"
onClick={ this.handleSwitchButtonClick }
>
<span className="ms-Icon ms-Icon--Switch" />
</button>
<button
className="minimize"
onClick={ this.handleMinimizeButtonClick }
>
<span className="ms-Icon ms-Icon--ChromeMinimize" />
</button>
</header>
<WebChat
className="react-web-chat"
onFetchToken={ this.handleFetchToken }
store={ store }
styleSet={ styleSet }
token={ token }
/>
{
locationRequested ?
<div>
<p id="display"></p>
<button onClick={this.handleLocationButtonClick}>
Gélolocation
</button>
</div>
:
<div></div>
}
</div>
}
</div>
);
}
}
When I click the button, I have this error:
And in the console:
What is wrong ??
First, there have been some updates to the a.minimizable-web-chat sample that are worth looking over. Refer to the code, however, as the README.md file has not been fully updated to reflect the changes.
As for your question, try the following changes. When tested, it works successfully for me. Change the component to a function and define store via useMem0().
import React, { useCallback, useMemo, useState } from 'react';
const MinimizableWebChat = () => {
const store = useMemo(
() =>
createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
language: window.navigator.language
}
}
});
} else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
if (action.payload.activity.from.role === 'bot') {
setNewMessage(true);
}
}
return next(action);
}),
[]
);
[...]
return (
[...]
<WebChat
[...]
store={store}
);
}
export default MinimizableWebChat;
Given the changes implemented in this file, it likely will impact how the other files function with it. My recommendation would be to do a wholesale update to bring your project in line with the current sample. It's really just this file, WebChat.js, and possibly App.js. There are supporting CSS files and the like that can be downloaded, if you don't have them.
Hope of help!

How to make external api calls in redux react

My code is as below:
const LOAD = 'redux-example/LOAD';
const LOAD_SUCCESS = 'redux-example/LOAD_SUCCESS';
const LOAD_FAIL = 'redux-example/LOAD_FAIL';
import axios from 'axios';
const initialState = {
loaded: false
};
export default function info(state = initialState, action = {}) {
switch (action.type) {
case LOAD:
return {
...state,
loading: true
};
case LOAD_SUCCESS:
return {
...state,
loading: false,
loaded: true,
data: action.result
};
case LOAD_FAIL:
return {
...state,
loading: false,
loaded: false,
error: action.error
};
default:
return state;
}
}
export function load() {
return {
types: [LOAD, LOAD_SUCCESS, LOAD_FAIL],
promise: (client) => client.get('http://example.com/getdata')
};
}
I am using https://github.com/erikras/react-redux-universal-hot-example example as starter kit. I want to make promise based api call to example.com/api.But I am not able to do it with async call.I get error in middleware that can not read promise of undefined.My middleware code is as below.
export default function clientMiddleware(client) {
return ({dispatch, getState}) => {
return next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
const { promise, types, ...rest } = action; // eslint-disable-line no-redeclare
if (!promise) {
return next(action);
}
const [REQUEST,SUCCESS,FAILURE] = types;
next({...rest, type: REQUEST});
const actionPromise = promise(client);
actionPromise.then(
(result) => next({...rest, result, type: SUCCESS}),
(error) => next({...rest, error, type: FAILURE})
).catch((error)=> {
console.error('MIDDLEWARE ERROR:', error);
next({...rest, error, type: FAILURE});
});
return actionPromise;
};
};
}
MY component code is as below
import React, {Component, PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {load} from 'redux/modules/info';
#connect(state => ({info: state.info.data}),dispatch => bindActionCreators({load}, dispatch))
export default class InfoBar extends Component {
static propTypes = {
info: PropTypes.object,
load: PropTypes.func.isRequired
}
render() {
const {info, load} = this.props; // eslint-disable-line no-shadow
const styles = require('./InfoBar.scss');
return (
<div className={styles.infoBar + ' well'}>
<div className="container">
This is an info bar
{' '}
<strong>{info ? info.message : 'no info!'}</strong>
<span className={styles.time}>{info && new Date(info.time).toString()}</span>
<button className="btn btn-primary" onClick={load}>Reload from server</button>
</div>
</div>
);
}
}
this is only the reducer. You would want to create an action. An action triggers the event that will make the redux store update its state. The basic flow of redux for something like this goes like:
Mount a component
Dispatch an action
Dispatched action in turn will update the store via the Provider component
this will trigger a re-render of the component.
The following is a basic example using fetch.
import fetch from 'isomorphic-fetch';
export function getUsers() {
return dispatch => {
dispatch({ type: REQUEST_USERS });
return fetch('/api/v1/users')
.then(res => res.json())
.then(users => {
dispatch({ type: RECEIVE_USERS, payload: users });
return users;
});
}
}
Then you can call this in your component level item.
import { getUsers } from 'actions/users';
class UserList extends Component {
componentDidMount() { dispatch(getUsers()) }
}
Check out the example