I'm facing an error that has been searching by myself for 2 days. But currently It's still not resolved, so I came here to ask If anyone ever faced this?
I'm using Redux toolkit in a sharepoint online project for passing data to each other components.
The first component worked perfectly, but when I use useSelector function for the 2nd one, this error appears
Although when I tried using console.log for each component, both are still receiving the data but
using data for the 2nd component will happen this error.
So has anyone ever faced this please help me out~, here is my codes
slice:
import { createSlice } from '#reduxjs/toolkit';
export interface titleState {
title: string;
}
const initialState: titleState = {
title : 'Your title'
};
export const titleSlice = createSlice({
name: 'title',
initialState,
reducers: {
SET_TITLE: (state, action) => {
state.title = action.payload;
}
}
});
export const { SET_TITLE } = titleSlice.actions;
export default titleSlice.reducer;
store
import { configureStore } from '#reduxjs/toolkit';
import titleReducer from "../features/titleSlice/titleSlice";
export const store: any = configureStore({
reducer: {
title: titleReducer
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
first component:
import { useSelector, useDispatch } from "react-redux";
import { AppDispatch, RootState } from "../../../../redux/store/store";
const FirstComponent: FunctionComponent<FirstComponent> = (
props
) => {
const STATE_TITLE = useSelector((state: RootState) => state.title);
console.log(STATE_TITLE);
const dispatch = useDispatch<AppDispatch>();
const handleTitle = (e) => {
dispatch(SET_TITLE(e.target.value));
setTitle(e.target.value);
}
return (
<div>
<textarea
onChange={handleTitle} //works fine
/>
</div>
}
second component:
import { useSelector, useDispatch } from "react-redux";
import { AppDispatch, RootState } from "../../../../redux/store/store";
const SecondComponent: FunctionComponent<ISecondComponentProps> = (props) => {
const TITLE_STATE = useSelector((state: RootState) => state.title)
console.log(TITLE_STATE)
return (
<div>
{YOUR_TITLE} //this line happens error
</div>
)
and here is the error from development tab :
The error happens because your TITLE_STATE is an object and not a string. Try changing the return statement of the second component to
<div>
{TITLE_STATE?.title}
</div>
If this works, the error was because you were trying to render objects directly. And investigate why your textarea component returns an object instead of string as value, since that is the root cause here
Related
I'm learning redux, and i've a method addPosts to add posts to the list of posts, and I'm doing it like this.
import { createSlice } from "#reduxjs/toolkit";
var initialState = [{ number: 1 }, { number: 2 }, { number: 3 }, { number: 4 }];
export const postsSlice = createSlice({
name: "postsSlice",
initialState,
reducers: {
addPost: (state, action) => {
state = [...state, action.payload];
},
},
});
export const allPosts = (state) => state.posts;
export const { addPost } = postsSlice.actions;
export default postsSlice.reducer;
and using the state like this.
import { useSelector, useDispatch } from "react-redux";
import { addPost, allPosts } from "./postsSlice";
function Posts() {
var posts = useSelector(allPosts);
var dispatch = useDispatch();
return (
<div>
{posts.map((post) => (
<div>{post.number}</div>
))}
{/* add post */}
<button
onClick={() => {
dispatch(addPost({ number: 1 }));
console.log(posts);
}}
>
addpost
</button>
</div>
);
}
export default Posts;
using state.push(action.payload) works somehow, altough the documentation says not use update state like this, and update in an immutable way.
like this state = [...state, action.payload]. it does not update state with this immutable way.
I don't know what is wrong that i'm doing.
thanks in advance for any help
You are misreading the wrong documentation for the wrong tool it seems - in a Redux Toolkit createSlice reducer, it is always 100% correct to use something like state.push to mutably modify the object in the state variable.
What you cannot do however is what you are trying here: reassign the state variable. That had never any effect in any kind of Redux reducer, unless you would return that state variable later.
If you want to do that, you will need to return [...state, action.payload] instead and leave the state variable alone altogether - it should not be reassigned.
But the recommended way would be that push.
For more, please read Writing Reducers with Immer
As per this instead of directly changing into state you can return in this way
return [...state, action.payload]
Depending on your definition of initialState
Please have a look into working example of react-redux-toolkit-slice-example
Below is the definition of slice
import { createSlice } from "#reduxjs/toolkit";
const initialState = [{ number: 1 }];
export const postsSlice = createSlice({
name: "postsSlice",
initialState,
reducers: {
addPost: (state, action) => {
return [...state, action.payload];
}
}
});
export const allPosts = (state) => state.posts || [];
export const { addPost } = postsSlice.actions;
export default postsSlice.reducer;
Defining the reducer(postSlice) in store
import { configureStore } from "#reduxjs/toolkit";
import postsReducer from "../features/posts/postsSlice";
export default configureStore({
reducer: {
posts: postsReducer
}
});
Use of slice in component
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { addPost, allPosts } from "./postsSlice";
const Posts = () => {
var posts = useSelector(allPosts);
var dispatch = useDispatch();
return (
<div>
{posts.map((post, key) => (
<div key={key}>{post.number}</div>
))}
{/* add post */}
<button
onClick={() => {
dispatch(
addPost({
number: Math.max(...posts.map(({ number }) => number)) + 1
})
);
console.log(posts);
}}
>
Add Post
</button>
</div>
);
};
export default Posts;
I have looked into multiple sources trying to solve this problem but could not find any answers. I have a functional component <Dashboard /> which will display some information from an API.
I expected the component to first get into useEffect, execute the getData function and then display {devices} on the screen. What happens, though, is that the store state is updated, but the component not. The {devices} variable is always undefined. I don't think I understand how to access my state variable from reducers/all/dashboard.js with useSelector.
dashboard/index.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import api from "../../services/api";
import * as DashboardActions from "../../store/actions/dashboard";
const Dashboard = (props) => {
const dispatch = useDispatch();
const devices = useSelector(state => state.device)
useEffect(() => {
async function getData() {
const pathname = "/dashboard";
await api
.get(pathname)
.then((res) => {
dispatch(DashboardActions.setData(res.data));
})
.catch((res) => {
console.log(res.response.data);
});
}
getData();
console.log("devices ue ", devices);
}, [dispatch]);
return (
<div>
<h1>Dashboard</h1>
<span>{devices}</span>
</div>
);
};
export default Dashboard;
reducers/all/dashboard.js
const INITIAL_STATE = {
devices: [],
};
function dashboard(state = INITIAL_STATE, action) {
console.log("Action ", action)
if ("DASHBOARD_SET_DATA" === action.type) {
const data = action.data;
console.log("Data: ", data.devices)
state = { ...state, devices: data.devices };
console.log("State ", state)
}
return state;
}
export default dashboard;
actions/dashboard.js
export function setData(data) {
return {
type: "DASHBOARD_SET_DATA",
data,
};
}
I would appreciate any help a lot.
Thanks in advance!
The react-redux useSelector hook is selecting state from your redux store state object.
If your dashboard reducer is combined into your root reducer, something like
const rootReducer = combineReducers({
... other reducers
dashboard,
... other reducers
});
Then the devices state value should be accessed from state.dashboard.devices.
The update for your component:
const devices = useSelector(state => state.dashboard.devices)
The common cause for my issue when researching this is mutating the state and not returning a new object of the state which causes redux to not recognize a change. However, this is not and has never been an issue and i'm well aware of it. I'm returning a new object. In the logger which you can see in the attached image it displays the successful api call resolved and the nextState is updated but never rendered. Refreshing the page acts exactly the same even though i expected to possibly need to do so upon initial landing to root page.
Component:
import pokemonReducer from '../../reducers/pokemon_reducer';
import PokemonIndexItem from './pokemon_index_item';
import {Route} from 'react-router-dom';
import PokemonDetailContainer from './pokemon_detail_container';
class PokemonIndex extends React.Component {
componentDidMount() {
this.props.requestAllPokemon();
}
render() {
const pokemon = this.props.pokemon;
return (
<section className="pokedex">
<Route path='/pokemon/:pokemonID' component={PokemonDetailContainer} />
<ul>{pokemon && pokemon.map(poke => <li>{poke.name}{poke.id}</li>)}</ul>
</section>
);
}
}
export default PokemonIndex;
and the container:
import {connect} from 'react-redux';
import { selectAllPokemon } from '../../reducers/selectors';
import PokemonIndex from './pokemon_index';
import { requestAllPokemon } from '../../actions/pokemon_actions';
const mapStateToProps = state => ({
pokemon: selectAllPokemon(state)
});
const mapDispatchToProps = dispatch => ({
requestAllPokemon: () => dispatch(requestAllPokemon())
});
export default connect(mapStateToProps, mapDispatchToProps)(PokemonIndex);
the reducer:
import { RECEIVE_ALL_POKEMON, RECEIVE_SINGLE_POKEMON} from '../actions/pokemon_actions';
const pokemonReducer = (initialState = {}, action) => {
Object.freeze(initialState);
switch(action.type) {
case RECEIVE_ALL_POKEMON:
return Object.assign({}, initialState, action.pokemon);
case RECEIVE_SINGLE_POKEMON:
let poke = action.payload.pokemon
return Object.assign({}, initialState, {[poke.id]: poke})
default:
return initialState;
}
};
export default pokemonReducer;
secondary reducer:
import { combineReducers } from 'redux';
import pokemonReducer from './pokemon_reducer'
const entitiesReducer = combineReducers({
pokemon: pokemonReducer,
});
export default entitiesReducer;
rootreducer:
import {combineReducers} from 'redux';
import entitiesReducer from './entities_reducer';
const rootReducer = combineReducers({
entities: entitiesReducer
});
export default rootReducer;
as requested here is the selectors defined in reducers folder
export const selectAllPokemon = (state) => {
Object.values(state.entities.pokemon);
};
export const selectSinglePokemon = (state) => {
Object.values(state.entities.pokemon)
};
and here is the actions created:
export const RECEIVE_ALL_POKEMON = "RECEIVE_ALL_POKEMON";
export const RECEIVE_SINGLE_POKEMON = "RECEIVE_SINGLE_POKEMON";
import * as APIUtil from '../util/api_util';
export const receiveAllPokemon = (pokemon) => (
{
type: RECEIVE_ALL_POKEMON,
pokemon
}
);
export const requestAllPokemon = () => (dispatch) => {
APIUtil.fetchAllPokemon()
.then(
pokemon =>
{ dispatch(receiveAllPokemon(pokemon));}
);
};
export const receiveSinglePokemon = data => (
{
type: RECEIVE_SINGLE_POKEMON,
data
}
);
export const requestSinglePokemon = id => (dispatch) => {
APIUtil.fetchSinglePokemon(id)
.then(pokemon => {dispatch(receiveSinglePokemon(pokemon));
return pokemon;});
};
nextstate showing in console
As you stated in your question, your redux state is getting properly set but your new state is never being rendered and I think this has to do with your selector. It looks to me that you forgot to return your computed state.
export const selectAllPokemon = (state) => {
Object.values(state.entities.pokemon);
};
// will return undefined
For returning your state you have two options:
Explicit return
export const selectAllPokemon = (state) => {
return Object.values(state.entities.pokemon);
};
Implicit return
export const selectAllPokemon = (state) => (
Object.values(state.entities.pokemon);
);
I refer to this article or look at the examples I created in playground to get a better unstanding of implicit and explicit return in arrow functions.
I try to fetch some data from the tvmaze api using react typescript and useContext, i can display the data but the useContext don't update with the return so when i use the map function nothing display any advice?
import React , { Fragment, useEffect, useContext, useState } from 'react'
import axios from 'axios'
import Store from '../Store/Store'
import "core-js/stable";
import "regenerator-runtime/runtime";
export default function App() {
const {state, dispatch} = useContext(Store)
useEffect(() => {
state.episodes.length === 0 && fetchDataAction()
})
const fetchDataAction = async () => {
const URL = 'http://api.tvmaze.com/singlesearch/shows?q=rick-&-morty&embed=episodes'
const data = await fetch(URL);
const dataJSON = await data.json();
console.log(dataJSON._embedded.episodes);
return dispatch({
type: "FETCH-DATA",
payload: dataJSON._embedded.episodes,
})
}
return (
<Fragment>
{console.log(state)}
<h1>Rick and Morty</h1>
<p>Pick your favorite episode!!!</p>
<section>
{state.episodes.map((episode: any) => {
return (
<section key={episode.id}>
<img src={episode.image.medium} alt={`Rick and Morty ${episode.name}`} />
<section>
Season: {episode.season} Number: {episode.number}
</section>
</section>
)
})}
</section>
</Fragment>
)
}
i'm not using redux but i'm using babel + webpack, i'm really new to all this so i'm a bit lost here my Store.tsx file,
import React from 'react'
interface IState {
episodes: [],
favorites: []
}
interface IAction {
type: string,
payload: any
}
const initialState:IState = {
episodes: [],
favorites: []
};
const Store = React.createContext<IState | any>(initialState)
function reducer(state: IState, action: IAction): IState {
switch (action.type) {
case 'FETCH_DATA':
return { ...state, episodes: action.payload}
default:
return state
}
}
export function StoreProvider(props: any): JSX.Element {
const [state, dispatch] = React.useReducer(reducer, initialState)
return <Store.Provider value={{state, dispatch}}>{props.children}</Store.Provider>
};
export default Store;
you have a typo. you are dispatching type: "FETCH-DATA", and your swich statement is checking for case 'FETCH_DATA'.
To avoid this problem in the future, common practice is to create a separate file where you would declare actions, in your case "FETCH_CASE". you can do literally const FETCH_ACTIONS = "FETCH_ACTIONS"
And then in your files where your reducer and dispatcher are, you would import the action to make sure they are referring to the same thing.
redux and webpack/babel are not used for the same purpose - redux is for store management, similar to useContext. webpack and babel compile your code, more or less.
there is no need to import axios if you are not using it, you are using fetch API for the same purpose you could use axios.
you have to declare fetchDataAction before you call it in the useEffect. in other words, useEffect should be below the function.
Im new to React and Redux and still kinda confused a little bit.
My goal is to render a bunch of json datas in the HTML by using GET request. I'm using react and redux to manage the state of the objects, but I believe my problem is that the data is not even there
so basically whenever someone request a URL /courses , he/she will see bunch of data in json.
I get the error in the component
TypeError: Cannot read property 'map' of undefined
Here's the code
Action
export function getCourses() {
return (dispatch) => {
return fetch('/courses', {
method: 'get',
headers: { 'Content-Type', 'application/json' },
}).then((response) => {
if (response.ok) {
return response.json().then((json) => {
dispatch({
type: 'GET_COURSES',
courses: json.courses
});
})
}
});
}
}
Reducer
export default function course(state={}, action) {
switch (action.type) {
case 'GET_COURSES':
return Object.assign({}, state, {
courses: action.courses
})
default:
return state;
}
}
Component
import React from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
class Course extends React.Component {
allCourses() {
return this.props.courses.map((course) => {
return(
<li>{ course.name }</li>
);
});
}
render() {
return (
<div>
<ul>
{ this.allCourses() }
</ul>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
courses: state.courses
}
}
export default connect(mapStateToProps)(Course);
Index reducer, where i combine everything
import { combineReducers } from 'redux';
import course from './course';
export default combineReducers({
course,
});
Configure Store , where i store the intial state and the reducer
import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
typeof window == 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
)
);
return store;
}
I believe why the data is not there is because i didn't call the action? any help would be appreciated.
mapStateToProps takes the root state as an argument (your index reducer, which is also the root reducer), not your course reducer. As far as I can tell this is the structure of your store:
-index <- This is the root reducer
-course
So to get the courses from that state, in your component:
// state is the state of the root reducer
const mapStateToProps = (state) => {
return {
courses: state.course.courses
}
}
Also, you might consider initialising the state of the course reducer with an empty array of courses, so if you have to render the component before the action is fired, you won't get the error.
const initialState = {
courses: []
};
export default function course(state= initialState, action) {
...
}
Finally, you're not firing the action at all, so you will never actually get the courses, I assume you want them to be retrieved once the Course component is loaded, for that you can use the componentDidMount event in your component.
First of all, you need to map the action to a property of the component
// Make sure you import the action
import { getCourses } from './pathToAction';
...
const mapDispatchToProps = (dispatch) => {
return {
onGetCourses: () => dispatch(getCourses())
};
}
// Connect also with the dispatcher
export default connect(masStateToProps, mapDispatchToProps)(Course);
Now call the onGetCourses property when the component mounts
class Course extends React.Component {
componentDidMount() {
this.props.onGetCourses();
}
...
}
its because props sometime can be undefined so you have to write a condtion like this
allCourses() {
if(this.props.courses){
return this.props.courses.map((course) => {
return(
<li>{ course.name }</li>
);
});
}
else {
return [];
}