I am creating the most basic example of applying Redux with React to practice what I have been learning last two days, simple application to get counter state from store and increment it, that is it.
The code does everything right, and it does dispatch an action (it does console log the action type and value also inside reducer also), but It doesn't increment counter.
If you are experienced with Redux you can check the reducer directly, I think the problem is there.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import {createStore} from "redux";
var initialState = {
count: 5
};
function reducer(state = initialState, action) {
console.log(action)
switch (action.type) {
case "ADD":
return {
count: state.count + action.value
};
default:
return state;
}
}
var store = createStore(reducer, initialState);
class App extends React.Component {
increment = () => {
store.dispatch({type: "ADD", value: 1});
};
render() {
return (
<div className="App">
<h1>Hello Counter Redux app</h1>
<button onClick={this.increment}> click to increment number</button>
<br />
<br />
<b> {store.getState().count} </b>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I would suggest a few revisions to your code - first, consider making use of react-redux to bind your store to your <App/> component.
Taking the react-redux based approach, you'll want to use the connect() method that this package provides, to connect your <App/> component to your actions and store:
const ConnectedApp = connect(
state => {
return {
count: state.count
};
},
dispatch => {
return {
increment: () => dispatch({ type: "ADD", value: 1 })
};
}
)(App);
Doing this exposes two additional props to your <App/> component: the count value (taken directly from store state) and the increment() function (which dispatches the action to your reducer). Notice that the connect() method returns a new version of your component <ConnectedAdd /> (which is used in the next step).
Next, you'll want to use the <Provider /> to mount your store to your app in the following way:
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
rootElement
);
Finally, a bit of refactoring to your <App/> component's render() method completes this process:
class App extends React.Component {
render() {
return (
<div className="App">
<h1>Hello Counter Redux app</h1>
<button onClick={this.props.increment}>
{" "}
click to increment number
</button>
<br />
<br />
<b> {this.props.count} </b>
</div>
);
}
}
The react redux "stuff" can be a bit to get your head around at first but once you've worked with it a bit, it starts to make more sense - stick at it :-)
For a full working example please see this codesandbox
Related
Goal:
For React TS.
The page List1 and List2 should use the same method named useFetch (retrieve data by using api link) by using generic approach by sending the interface (named Client and TheComputer) to the useFetch.
Each interface has different datamember.
You should enable to use useFetch by many and different interface's name.
In other words,
UseFetch should be a independent tool that can be used by different interface by sending api link and interface asa parameter.
Problem:
You are enable to use react js to achieve it (without using syntax interface) but not for React TS.
I have problem to make useFetch as a independent component with react TS. How should it be solved?
Other info:
*It is achieved for ReactJS but not for ReactTS.
*Somehow it doesn't work in my local computer probably due to strictly linting and TS error.
You need to use interface to order to retrieve data and then display it.
*Newbie in ReactTS
Thank you!
Stackblitz:
JS
https://stackblitz.com/edit/react-mjvs38?
TS
https://stackblitz.com/edit/react-ts-7oeqen?
index.tsx
import React, { Component } from 'react';
import { render } from 'react-dom';
import {
BrowserRouter as Router,
Link,
Route,
Routes,
useParams,
} from 'react-router-dom';
import './style.css';
import useFetch1 from './useFetchTS1';
import useFetch2 from './useFetchTS2';
function App() {
return (
<div>
<h1>Home</h1>
</div>
);
}
function List1() {
const { data, loading, error } = useFetch1('https://api.github.com/users');
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<img src={item.avatar_url} />
<div>{item.id}</div>
</div>
))}
;
</div>
);
}
function List2() {
const { data, loading, error } = useFetch2(
'https://jsonplaceholder.typicode.com/todos'
);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<div>
Id: {item.id} Title: {item.title}
</div>
</div>
))}
;
</div>
);
}
render(
<Router>
<div>
<header>
<Link to="/">Home</Link>
<br />
<Link to="/list1">List1</Link>
<br />
<Link to="/list2">List2</Link>
<br />
<hr />
</header>
<Routes>
<Route path="/" element={<App />} exact></Route>
<Route path="/list1" element={<List1 />} exact></Route>
<Route path="/list2" element={<List2 />}></Route>
</Routes>
</div>
</Router>,
document.getElementById('root')
);
useFetchTS1.tsx
import { useState, useEffect } from 'react';
interface Client {
id: number;
avatar_url: string;
}
export default function useFetch1(url) {
const [data, setData] = useState<Client[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
}
useFetchTS2.tsx
import { useState, useEffect } from 'react';
interface TheComputer {
id: number;
title: string;
}
export default function useFetch2(url) {
const [data, setData] = useState<TheComputer[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
}
There is a design that used to be called the Service Agent pattern that may work well for you:
Use React in the standard way, with useEffect etc
Views simply get type safe data and update their model
Views know nothing about APIs and just ask the agent class
The agent class can express the API interface
A lower level fetch class can do plumbing in a shared way
For sonething to compare against, see if any of my code is useful:
View classes
API classes
In these examples:
CompaniesContainer is the view class
ApiFetch sends and receives any type of API payload, and does common tasks such as refreshing OAuth tokens
ApiClient ensures that views use only type safe requests and responses
You can adapt some of this into a React hook if you prefer. Personally though I prefer to limit React syntax to view logic, and use plain Typescript classes in other places. I can then use equivalent classes in other types of UI, such as mobile apps.
So I believe we can get the useFetch hook to be generic for you if we change it to the following:
import { useEffect, useState } from 'react';
export default function useFetch1<TData = any>(url) {
const [data, setData] = useState<TData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
We use a generic <TData = any> in the function definition and TData[] in the useState hook for data.
Then in your index.tsx file you can define the interfaces there, and pass them to the generic useFetch1 hook like this:
useFetch1<Client>('https://api.github.com/users');
and
useFetch1<TheComputer>('https://jsonplaceholder.typicode.com/todos');
This lets you have the useFetch hook be generic, and still get the data returned to be the correct Interface/Type.
Your updated index.tsx file would look like this:
import './style.css';
import {
Link,
Route,
BrowserRouter as Router,
Routes,
useParams,
} from 'react-router-dom';
import React, { Component } from 'react';
import { render } from 'react-dom';
import useFetch1 from './useFetchTS1';
interface Client {
id: number;
avatar_url: string;
}
interface TheComputer {
id: number;
title: string;
}
function App() {
return (
<div>
<h1>Home</h1>
</div>
);
}
function List1() {
const { data, loading, error } = useFetch1<Client>('https://api.github.com/users');
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<img src={item.avatar_url} />
<div>{item.id}</div>
</div>
))}
;
</div>
);
}
function List2() {
const { data, loading, error } = useFetch1<TheComputer>(
'https://jsonplaceholder.typicode.com/todos'
);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<div>
Id: {item.id} Title: {item.title}
</div>
</div>
))}
;
</div>
);
}
render(
<Router>
<div>
<header>
<Link to="/">Home</Link>
<br />
<Link to="/list1">List1</Link>
<br />
<Link to="/list2">List2</Link>
<br />
<hr />
</header>
<Routes>
<Route path="/" element={<App />} exact></Route>
<Route path="/list1" element={<List1 />} exact></Route>
<Route path="/list2" element={<List2 />}></Route>
</Routes>
</div>
</Router>,
document.getElementById('root')
);
This utilizes generic types: https://www.typescriptlang.org/docs/handbook/2/generics.html
I updated the stackblitz too and seems to be working: https://stackblitz.com/edit/react-ts-zasl3r?file=useFetchTS1.tsx
I am having a TypeError (TypeError: Object(...) is not a function) when I want to dispatch an action. I'm not using any middleware and don't know what I can do to solve it. I had this error already yesterday but somehow managed to solve it (i donk know how i did this)
This is the App.js:
import React from "react";
import { store } from "../store";
import { withdrawMoney} from "../actions";
const App = () => {
return (
<div className="app">
<div className="card">
<div className="card-header">
Welcome to your bank account
</div>
<div className="card-body">
<h1>Hello, {store.getState().name}!</h1>
<ul className="list-group">
<li className="list-group-item">
<h4>Your total amount:</h4>
{store.getState().balance}
</li>
</ul>
<button className="btn btn-primary card-link" data-amount="5000" onClick={dispatchBtnAction}>Withdraw $5,000</button>
<button className="btn btn-primary card-link" data-amount="10000" onClick={dispatchBtnAction}>Witdhraw $10,000</button>
</div>
</div>
</div>
);
}
function dispatchBtnAction(e) {
store.dispatch(withdrawMoney(e.target.dataset.amount));
}
export default App;
Here is the actioncreator:
function withdrawMoney(amount) {
return {
type: "ADD_TODO",
amount
}
}
If you need here is the reducer:
export default (state, action) => {
console.log(action);
return state
}
As you can see I'm a very new to redux but I'd like to know what mistake I make all the time when dispatching an action. Thanks
I believe the issue is that you aren't exporting the withdrawMoney function, so you aren't able to call it in the component that you're attempting to import into.
try:
export function withdrawMoney(amount) {
return {
type: "ADD_TODO",
amount
}
}
Another subtle mistake that will cause this error is what I tried to do, don't accidentally do this:
import React, { useSelector, useState ... } from 'react'
it should be:
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
Try to install :
npm i react#next react-dom#next and run again
const mapStateToProps = state => ({
Bank: state.Bank,
});
function mapDispatchToProps(dispatch) {
return {
dispatch,
...bindActionCreators({ getBanks, addBank }, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(BankComponent);
this worked like a Charm for me .
bit Late to party,
to me it was about casing.
export const addTodo = text => ({
type: ADD_TODO,
desc: text
});
export const removeTodo = id=> ({
type: REMOVE_TODO,
id: id
})
export const increaseCount = () => ({
type: INCREASE_COUNT
});
export const decreaseCount = () => ({
type: DECREASE_COUNT
})
when I renamed all those like
export const AddTodo = text => ({
type: ADD_TODO,
desc: text
});
export const RemoveTodo = id => ({
type: REMOVE_TODO,
id: id
})
export const IncreaseCount = () => ({
type: INCREASE_COUNT
});
export const DecreaseCount = () => ({
type: DECREASE_COUNT
})
it worked.
I spent hours debugging this, turns out I was doing this:
import React, { connect } from "react";
Instead of
import React from "react";
import { connect } from "react-redux";
It's weird the former didn't throw an error, but it will cause the problem!
First problem I see right off the bat is your reducer is not setup to do anything with the dispatched withdrawMoney action creator.
export default (state, action) => {
switch (action.type) {
case "ADD_TODO": {
return {
...state,
amount: action.amount,
};
}
default:
return state;
}
};
If this does not help, the code from your other files would be helpful.
It may not be the main issue, but it looks like you're not using the React-Redux library to work with the store. You can reference the store directly in your React components, but it's a bad practice. See my explanation for why you should be using React-Redux instead of writing "manual" store handling logic.
After updating react-redux to version 7.2.0+ I was receiving this error anytime I wrote:
const dispatch = useDispatch()
I stopped my application and re-ran it with npm start, and everything is working now.
I was using redux toolkit and for me the problem was an extra '}'
such that my 'reducers' object, in 'createSlice' function, already had a closing curly brace before my second reducer
and my 2nd reducer was actually outside the 'reducers' object,
making it not a reducer and hence not working even when you export or import it properly.
So the problem is not in your export or import, but actually where your function is defined.
This may be different for other users, but in my case this turned out to be the cause of this error.
Goal
To click the next button and dispatch two actions to the redux store that:
Firstly, update the skipAmount value.
And then use the updated skipAmount value to generate apiQuery (a string that is being used to make a request to a server).
Problem
The skipAmount value is not being updated between action 1 & 2
Example
I have created a CodeSandbox that clear demonstrates the issue that I am having. Notice that the skipAmount value is 100 (or one click event) ahead of apiQuery.
https://codesandbox.io/s/o2vvpwqo9
Code
Index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import App from "./App";
import reducer from "./reducer";
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js
import React from 'react';
import { connect } from 'react-redux';
const mapStateToProps = state => ({
...state,
});
const queryGenerator = props => `www.apiExample.com?skipAmount=${props.skipAmount}`;
const ConnectedApp = props => (
<div className="App">
<button
onClick={() => {
props.dispatch({ type: 'SET_SKIP_AMOUNT_PLUS_100' });
props.dispatch({ type: 'SET_API_QUERY', payload: queryGenerator(props) });
}
}
>
Next
</button>
<p>Skip amount on redux: {props.skipAmount}</p>
<p>Query being generated: {props.apiQuery}</p>
</div>
);
export default connect(mapStateToProps)(ConnectedApp);
reducer.js
const reducerDefaultState = {
skipAmount: 0,
apiQuery: 'www.apiExample.com',
};
export default (state = reducerDefaultState, action) => {
switch (action.type) {
case 'SET_SKIP_AMOUNT_PLUS_100':
return {
...state,
skipAmount: state.skipAmount + 100,
};
case 'SET_API_QUERY':
return {
...state,
apiQuery: action.payload,
};
default:
return state;
}
};
In App.js queryGenerator(props) you are passing the unchanged props from the onClick.
props are'nt changing from SET_SKIP_AMOUNT_PLUS_100 until rerender.
onClick={() => {
props.dispatch({ type: 'SET_SKIP_AMOUNT_PLUS_100' });
props.dispatch({ type: 'SET_API_QUERY', payload: queryGenerator(props) });
}
In 'SET_SKIP_AMOUNT_PLUS_100' you are changing the redux state. (not the current props in component),
and in 'SET_API_QUERY' your are using the components props (not what's in redux) because props has'nt updated yet.
I'm doing a Redux tutorial, and don't understand something in it. I have the following container:
import React, { Component } from 'react';
import CommentsList from "./comments_list";
import { connect } from 'react-redux';
import * as actions from '../actions';
class CommentBox extends Component {
constructor(props) {
super(props);
this.state = { comment: '' };
}
handleChange(event) {
this.setState({ comment: event.target.value })
}
submitButton(e) {
e.preventDefault();
this.props.saveComment(this.state.comment);
this.setState({ comment: '' });
}
render () {
return(
<div>
<form onSubmit={(e) => this.submitButton(e)} className="comment-box">
<textarea
value={this.state.comment}
onChange={(e) => this.handleChange(e)} />
<button action="submit">Submit</button>
</form>
<CommentsList comment={this.state.comment}/>
</div>
);
}
}
export default connect(null, actions)(CommentBox);
This container uses:
import * as actions from '../actions';
and on the bottom of the file:
export default connect(null, actions)(CommentBox);
I'm used to using mapStateToProps and mapDispatchToProps, but here only the actions are imported, and then used in the submitButton(e) method:
this.props.saveComment(this.state.comment);
The saveComment comes from the actions/index.js file:
import { SAVE_COMMENT } from './types';
export function saveComment(comment) {
return {
type: SAVE_COMMENT,
payload: comment
}
}
Can I always use this.props to call a function from the actions/index.js file? Why don't I need to use the mapStateToProps first?
Can I always use this.props to call a function from the actions/index.js file?
Yes. From the react-redux docs:
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props.
connect is wrapping the exports from actions/index.js with dispatch calls for you.
Why don't I need to use the mapStateToProps first?
Because mapStateToProps and mapDispatchToProps are used for different purposes and mapped separately before being merged together and injected into your component.
If either return undefined or null, they are ignored. In the case of mapStateToProps, it also means the component won't subscribe to updates from the store. Again, from the react-redux docs:
If you don't want to subscribe to store updates, pass null or undefined in place of mapStateToProps.
Newbie here trying to learn some Redux.
GOAL: to get a button to click and login/logout, updating the store as true/false status whichever way.
const store = createStore(myReducer)
Created my store, passing in my reducer.
This has a default state of logged out. And returns the opposite, whenever the button is clicked.
I know this action works through debugging.
function myReducer(state = { isLoggedIn: false }, action) {
switch (action.type) {
case 'TOGGLE':
return {
isLoggedIn: !state.isLoggedIn
}
default:
return state
}
}
The problem starts here - when i try to access the store.getState() data.
class Main extends React.Component {
render() {
return (
<div>
<h1>Login Status: { state.isLoggedIn }</h1>
<button onClick={this.props.login}>Login</button>
</div>
)
}
}
const render = () => {
ReactDOM.render(<Main status={store.getState().isLoggedIn} login={() => store.dispatch({ type: 'TOGGLE' })}/>, document.getElementById('root'));
}
store.subscribe(render);
render();
I've tried store.getState().isLoggedIn & store.getState() & this.props.status and then assigning the store.getState().isLoggedIn in the Main component - but nothing works.
Can anyone tell me where i'm going wrong?
You don't directly access the store using getState to find data. The Redux docs explain the process in-depth, but basically you'll connect each component to the Redux store using connect method of the react-redux package.
Here's an example of how this could work for your above component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Main from '../components/Main'
class MainContainer extends Component {
render() {
return <Main {...this.props} />
}
}
const mapStateToProps = state => ({
isLoggedIn: state.isLoggedIn,
})
const mapDispatchToProps = dispatch => ({
login() {
dispatch({type: 'TOGGLE'})
},
})
MainContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(MainContainer)
export default MainContainer
You would then want to render the MainContainer in place of the Main component. The container will pass down isLoggedIn and login as props to Main when it renders it.