I am making auth component using react-native.
And the code below sends to 'MainTab' of 'this.props.navigation' depends on the result of axios.
<TouchableOpacity onPress={this.handleSubmit}>
<Text>Save</Text>
</TouchableOpacity>
handleSubmit = () => {
const result = await axios.post(
'http://192.0.0.1:4000/clients',
users
);
if (result.data.success) {
return this.props.navigation.navigate('MainTab');
}
return false
};
But I want to use handleSubmit at an other 'js' file to avoid doing repeatedly.
Thus, I edit a code like below.
import { saveSettings } from '../../storage/settingsStorage'
handleSubmit(): void {
saveSettings(this.state);
}
// in 'settingsStorage.js'
export const saveSettings = async users => {
try {
const result = await axios.post(
'http://192.0.0.1:4000/clients/token',
users
);
if (result.data.success) {
return this.props.navigation.navigate('MainTab');
}
return false
} catch (e) {
console.log(e);
}
};
And in this case, I know 'this.props' can't be passed in normal Js file without passing props. But I don't know how can I pass the props?
Thank you so much for reading this.
Based on your description I think you can just add a second parameter to saveSettings and pass the navigation object through:
import { saveSettings } from '../../storage/settingsStorage'
handleSubmit(): void {
saveSettings(this.state, this.props.navigation);
}
// in 'settingsStorage.js'
export const saveSettings = async (users, navigation) => {
try {
const result = await axios.post(
'http://192.0.0.1:4000/clients/token',
users
);
if (result.data.success) {
return navigation.navigate('MainTab');
}
return false
} catch (e) {
console.log(e);
}
};
Instead of passing navigation prop you can use a technique Navigating without the navigation prop as described in official site .
App.js
import { createStackNavigator, createAppContainer } from 'react-navigation';
import NavigationService from './NavigationService';
const TopLevelNavigator = createStackNavigator({
/* ... */
});
const AppContainer = createAppContainer(TopLevelNavigator);
export default class App extends React.Component {
// ...
render() {
return (
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
);
}
}
we define NavigationService which is a simple module with functions that dispatch user-defined navigation actions.
/ NavigationService.js
import { NavigationActions } from 'react-navigation';
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
// add other navigation functions that you need and export them
export default {
navigate,
setTopLevelNavigator,
};
Now use it every where without navigation prop
// any js module
import NavigationService from 'path-to-NavigationService.js';
// ...
NavigationService.navigate('MainTab');
Related
Below is the code located at "Pages/home.js". // localhost:3000/home
import axios from 'axios';
import Section1 from '../components/home-sections/section-1';
const Homepage = ({ show }) => {
const Html = JSON.parse(show.response.DesktopHTML);
const renderSection = () => {
return Html.map((itemData,index)=>{
return(<div key={index}>{itemData.DisplayName}</div>)
})
}
return(
<div>
{ renderSection()}
<Section1 />
</div>
)
}
export const getServerSideProps = async ({ query }) => {
try {
const response = await axios.get(
`https://api.example.com/getHomeSection?title=Section 1`
);
return {
props: {
show: response.data,
},
};
} catch (error) {
return {
props: {
error: error.error,
},
};
}
};
export default Homepage;
Now same code I added into section-1.js and this file is located to "components/home-sections/section-1.js"
Now getServerSideProps is working fine in home.js, but in section-1.js it is not working.
Error: TypeError: show is undefined in section-1.js
You cannot use getServerSideProps in non-page components. You can either pass the prop from Home to HomeSection or create a context so the value can be available globally from the component tree
getServerSideProps can only be exported from a page. You can’t export
it from non-page files.
https://nextjs.org/docs/basic-features/data-fetching#only-allowed-in-a-page-2
getServerSideProps can only be exported from Page components. It will not be run on components imported into a page.
However, you could export a function from the component that returns the props, and call that function from the page's getServerSideProps function.
Create a getServerSideProps function on the component.
// #components/MyComponent.tsx
import { GetServerSidePropsContext } from 'next';
function MyComponent(props: IMyComponentProps) {
return (<div>MyComponent</div>;)
}
MyComponent.getServerSideProps = async (context: GetServerSidePropsContext): Promise<{ props: IMyComponentProps }> => {
return { props: { ... } };
}
export default MyComponent;
In your page's getServerSideProps function, call the component's getServerSideProps function and merge the props from the component with the props from the page.
// mypage.tsx
import MyComponent from '#components/MyComponent';
const Page: NextPageWithLayout = (props: IIndexPageProps) => {
return <MyComponent />;
}
export async function getServerSideProps(context: GetServerSidePropsContext): Promise<{ props: IIndexPageProps }> {
let componentServerSideProps = await MyComponent.getServerSideProps(context);
let otherServerSideProps = { props: { ... } };
return {
props: {
...componentServerSideProps.props,
...otherServerSideProps.props
}
};
}
I am a newbie in React and Next JS, I want to set initial auth user data on initial load from the __app.js. But using dispatch throwing error "Invalid hook call". I know according to docs calling hooks in render function is wrong. but I am looking for an alternate solution to this.
How I can set auth data one-time so that will be available for all the pages and components.
I am including my code below.
/contexts/app.js
import { useReducer, useContext, createContext } from 'react'
const AppStateContext = createContext()
const AppDispatchContext = createContext()
const reducer = (state, action) => {
switch (action.type) {
case 'SET_AUTH': {
return state = action.payload
}
default: {
throw new Error(`Unknown action: ${action.type}`)
}
}
}
export const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, {})
return (
<AppDispatchContext.Provider value={dispatch}>
<AppStateContext.Provider value={state}>
{children}
</AppStateContext.Provider>
</AppDispatchContext.Provider>
)
}
export const useAuth = () => useContext(AppStateContext)
export const useDispatchAuth = () => useContext(AppDispatchContext)
/_app.js
import 'bootstrap/dist/css/bootstrap.min.css'
import '../styles/globals.css'
import App from 'next/app'
import Layout from '../components/Layout'
import { mutate } from 'swr'
import { getUser } from '../requests/userApi'
import { AppProvider, useDispatchAuth } from '../contexts/app'
class MyApp extends App {
render() {
const dispatchAuth = useDispatchAuth()
const { Component, pageProps, props } = this.props
// Set initial user data
const setInitialUserData = async () => {
if (props.isServer) {
const initialData = {
loading: false,
loggedIn: (props.user) ? true : false,
user: props.user
}
const auth = await mutate('api-user', initialData, false)
dispatchAuth({
type: 'SET_AUTH',
payload: auth
})
}
}
//----------------------
// Set initial user data
setInitialUserData()
//----------------------
return (
<AppProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</AppProvider>
)
}
}
MyApp.getInitialProps = async (appContext) => {
let isServer = (appContext.ctx.req) ? true : false
let user = null
let userTypes = {}
// Get user server side
if (isServer) {
await getUser()
.then(response => {
let data = response.data
if (data.status == true) {
// Set user
user = data.data.user
userTypes = data.data.user_types
//---------
}
})
.catch(error => {
//
})
}
//---------------------
return {
props: {
user,
userTypes,
isServer
}
}
}
export default MyApp
I believe this is the intended use of the useEffect hook with an empty array as its second argument:
https://reactjs.org/docs/hooks-effect.html
import {useEffect} from 'react'
class MyApp extends App {
useEffect(()=> {
setInitialUserData()
},[])
render() {
...
}
}
I want the function FetchAll.getAll() to be called from the AddTracker.addOne() method
Here's the UI component I'm exporting called FetchAll with the method called getAll
import React, { Component } from "react";
export default class FetchAll extends Component {
state = {
loading: true,
trackers: null,
};
getAll = async () => {
let res = await fetch("http://localhost:8181/trackers");
const json = await res.json();
this.setState({ trackers: json.data, loading: false });
};
componentDidMount() {
this.getAll();
}
render() {
return (
<>
{this.props.render(
this.state.loading,
this.state.trackers,
this.getAll
)}
</>
);
}
}
The Navbar Component that's importing FetchAll then passing in the getAll prop to the AssTracker component
import React from "react";
import { AddTracker, FetchAll } from "./index";
import { Navbar} from "react-bootstrap";
export default function NavComponent(props) {
return (
<>
<Navbar >
<FetchAll
render={(loading, trackers, getAll) => {
return <AddTracker getAll={getAll} />;
}}
/>
</Navbar>
</>
);
}
The AddTracker component where I want the props.getAll function to be called
import React, { useState } from "react";
import { Form, FormControl, Button } from "react-bootstrap";
const DB = "http://localhost:8181/trackers";
export default function AddTracker(props) {
function formClick(e) {
e.preventDefault();
let dataObj = { url: formData };
const addOne = async () => {
let res = await fetch(DB, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(dataObj),
}).catch((err) => console.log(err));
const text = await res.text();
console.log(text);
return text;
};
const runFirst = async () => {
await addOne();
props.getAll(); // nothing happens
console.log(props.getAll()); // Promise {<pending>}
console.log(props); // {getAll: f}
};
runFirst();
}
const [formData, setFormData] = useState();
return (
<>
<Form inline>
<FormControl
type="text"
onChange={(e) => {
setFormData(e.target.value);
}}
/>
<Button variant="primary" onClick={(e) => formClick(e)}>
Add Tracker
</Button>
</Form>
</>
);
}
I can not get props.getAll() to be called inside of AddTracker. I've tried calling it directly inside of addOne, I've tried extracting it to a constant in the outer scope per vscode. I'm not sure what else to try.I've got it working in another component which makes it all the more perplexing.
Any help would be appreciated.
Thanks!
Itʼs not clear from your question what problem you are encountering, but I notice that your addOne() function has a props parameter that shadows the props of the AddTracker component. So Iʼm guessing props.getAll is undefined where you try to call it because getAll() is a property of the outer (shadowed) props.
The eslint no-shadow rule is helpful for making this kind of problem more immediately obvious.
I am trying to migrate my previously working local state to redux. Now loading available Players works just fine, but deleting will somehow stop in the playerActions.js file, where I dispatch and then return an API Call. So to further give details here are my code parts in relevance:
PlayerPage.js (Component):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { loadPlayers, deletePlayer } from '../../redux/actions/playerActions';
import PlayerForm from './playerform';
import PlayCard from './playercard';
import PropTypes from 'prop-types';
import { toast } from 'react-toastify';
class PlayerPage extends Component {
constructor(props) {
super(props);
this.handleDeletePlayer = this.handleDeletePlayer.bind(this);
state = {};
componentDidMount() {
const players = this.props;
players.loadPlayers().catch(err => {
alert('Loading players failed. ' + err);
});
}
handleDeletePlayer = player => {
toast.success('Player deleted');
try {
deletePlayer(player);
} catch (err) {
toast.error('Delete failed. ' + err.message, { autoClose: false });
}
};
render() {
const styles = {
margin: '20px'
};
return (
<div className="container-fluid">
<div>
<h2 style={styles}>Add Player</h2>
<div className="container-fluid">
<PlayerForm handleAddNewPlayer={this.handleAddPlayer} />
</div>
</div>
<hr></hr>
<div>
<h2 style={styles}>Available Player</h2>
<div className="container-fluid">
{this.props.players.map(player => (
<PlayCard
player={player}
key={player.id}
imageSource={`${process.env.API_URL}/${player.profileImg}`}
onDeletePlayer={this.handleDeletePlayer}
/>
))}
</div>
</div>
</div>
);
}
}
PlayerPage.propTypes = {
players: PropTypes.array.isRequired
};
function mapStateToProps(state) {
return {
players: state.players
};
}
const mapDispatchToProps = {
loadPlayers,
deletePlayer
};
export default connect(mapStateToProps, mapDispatchToProps)(PlayerPage);
And the Action being called is in here:
playerActions.js:
import * as types from './actionTypes';
import * as playerApi from '../../api/playerApi';
export function loadPlayersSuccess(players) {
return { type: types.LOAD_PLAYERS_SUCCESS, players };
}
export function deletePlayerOptimistic(player) {
return { type: types.DELETE_PLAYER_OPTIMISTIC, player };
}
export function loadPlayers() {
return function(dispatch) {
return playerApi
.getAllPlayers()
.then(players => {
dispatch(loadPlayersSuccess(players));
})
.catch(err => {
throw err;
});
};
}
export function deletePlayer(player) {
console.log('Hitting deletePlayer function in playerActions');
return function(dispatch) {
dispatch(deletePlayerOptimistic(player));
return playerApi.deletePlayer(player);
};
}
The console.log is the last thing the app is hitting. But the API Call is never made though.
API Call would be:
playerApi.js:
import { handleResponse, handleError } from './apiUtils';
const axios = require('axios');
export function getAllPlayers() {
return (
axios
.get(`${process.env.API_URL}/player`)
.then(handleResponse)
.catch(handleError)
);
}
export function deletePlayer(id) {
return (
axios
.delete(`${process.env.API_URL}/player/${id}`)
.then(handleResponse)
.catch(handleError)
);
}
I was like spraying out console.log in different places and files and the last one I am hitting is the one in playerActions.js. But after hitting it the part with return function(dispatch) {} will not be executed.
So if someone could point me in a general direction I'd be more than grateful.
It looks like you are calling your action creator deletePlayer but you aren't dispatching it correctly. This is why the console.log is being called but not the method that does the request.
I'd recommend taking a look at the documentation for mapDispatchToProps to fully understand how this works. In your example, you should just need to change the call to deletePlayer in your PlayerPage component to this.props.deletePlayer() to use the action creator after it's been bound to dispatch properly.
this how the mapDispatchToProps should be:
const mapDispatchToProps = dispatch => {
return {
load: () => dispatch(loadPlayers()),
delete: () => dispatch(deletePlayer()),
}
}
then call load players with this.props.load() and delete player with this.props.delete()
I want my component to fetch an array of objects from the server. Each object is a message with author, body and date. I then want to render these messages in my react component.
My react component currently fetches data from the server before mounting. It will then store this message list in the redux state.|
I'm sure there's a better way of writing this code.
1. Can I place the fetch request in either the Action or Reducer file?
2. Can I write a function in the component to make the async call?
import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from '../actions/actions_index.js';
class MessageList extends Component {
constructor(props) {
super(props)
}
componentWillMount() {
fetch('https://wagon-chat.herokuapp.com/general/messages')
.then(response => response.json(),
error => console.log('An error occured receiving messages', error))
.then((data) => {
this.props.fetchMessages(data.messages);
});
}
render() {
return (
<div className="message-list">
{this.props.messageList.map( (message, index) => { return <Message key={index} message={message}/> })}
</div>
)
}
}
function mapStateToProps(state) {
return {
messageList: state.messageList
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{ fetchMessages: fetchMessages },
dispatch
)
}
export default connect(mapStateToProps, mapDispatchToProps)(MessageList);
Can I place the fetch request in either the Action or Reducer file?
The fetch request should be placed in action creator. Where the retrieved data will be dispatched to reducer later to manipulate the data, and lastly update the store to show on UI. Here's simple flow for most of react-redux app.
UI -> Action creator (calling request, saga etc..) -> reducer -> store -> UI
Can I write a function in the component to make the async call?
Yes, this should be called action creator, and you can see actions.js below for more reference.
I think you can safely follow this sample pattern where most tutorials out there apply. I'm assuming all files listed here are in the same directory.
constant.js
const MESSAGE_FETCH__SUCCESS = 'MESSAGE/FETCH__SUCCESS'
const MESSAGE_FETCH__ERROR = 'MESSAGE/FETCH__ERROR'
export {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
}
actions.js
import {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
} from './constant';
const fetchMessageError = () => ({
type: MESSAGE_FETCH__ERROR
})
const fetchMessageSuccess = data => ({
type: MESSAGE_FETCH__SUCCESS,
payload: data
})
const fetchMessages = () => {
const data = fetch(...);
// if error
if (data.error)
fetchMessageError();
else fetchMessageSuccess(data.data);
}
export {
fetchMessages
}
reducers.js
import {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
} from './constant';
const INIT_STATE = {
messageList: []
}
export default function( state = INIT_STATE, action ) {
switch(action.type) {
case MESSAGE_FETCH__SUCCESS:
return {
...state,
messageList: action.payload
}
case MESSAGE_FETCH__ERROR:
// Do whatever you want here for an error case
return {
...state
}
default:
return state;
}
}
index.js
Please read the comment I noted
import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from './actions';
class MessageList extends Component {
/* If you don't do anything in the constructor, it's okay to remove calling `constructor(props)`
*/
//constructor(props) {
// super(props)
//}
// I usually put this async call in `componentDidMount` method
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div className="message-list">
{
/* Each message should have an unique id so they can be used
for `key` index. Do not use `index` as an value to `key`.
See this useful link for more reference: https://stackoverflow.com/questions/28329382/understanding-unique-keys-for-array-children-in-react-js
*/
this.props.messageList.map( message => <Message key={message.id} message={message}/> )
}
</div>
)
}
}
function mapStateToProps(state) {
return {
messageList: state.messageList
}
}
export default connect(mapStateToProps, {
fetchMessages
})(MessageList);
You could use redux-thunk in an action called getMessages.
So:
(The double arrow func, is to return an action, see redux-thunk)
const getMessages = ()=>(dispatch, getState)=>{
fetch('https://wagon-chat.herokuapp.com/general/messages')
.then(response => response.json(),
error => dispatch(['error', error]))
.then((data) => {
dispatch(data);
})
}
Then you've successfully reduced your component to:
componentWillMount(){
this.props.getMessages()
}
I think #Duc_Hong answered the question.
And in my opinion, I suggest using the side-effect middle-ware to make AJAX call more structured, so that we could handle more complicated scenarios (e.g. cancel the ajax request, multiple request in the same time) and make it more testable.
Here's the code snippet using Redux Saga
// Actions.js
const FOO_FETCH_START = 'FOO\FETCH_START'
function action(type, payload={}) {
return {type, payload};
}
export const startFetch = () => action{FOO_FETCH_START, payload);
// reducer.js
export const foo = (state = {status: 'loading'}, action) => {
switch (action.type) {
case FOO_FETCH_STARTED: {
return _.assign({}, state, {status: 'start fetching', foo: null});
}
case FOO_FETCH_SUCCESS: {
return _.assign({}, state, {status: 'success', foo: action.data});
}
......
}
};
Can I place the fetch request in either the Action or Reducer file?
// Saga.js, I put the ajax call (fetch, axios whatever you want) here.
export function* fetchFoo() {
const response = yield call(fetch, url);
yield put({type: FOO_FETCH_SUCCESS, reponse.data});
}
// This function will be used in `rootSaga()`, it's a listener for the action FOO_FETCH_START
export function* fooSagas() {
yield takeEvery(FOO_FETCH_START, fetchFoo);
}
Can I write a function in the component to make the async call?
// React component, I trigger the fetch by an action creation in componentDidMount
class Foo extends React.Component {
componentDidMount() {
this.props.startFetch();
}
render() {
<div>
{this.props.foo.data ? this.props.foo.data : 'Loading....'}
<div>
}
}
const mapStateToProps = (state) => ({foo: state.foo});
const mapDispatchToProps = { startFetch }
export default connect(mapStateToProps, mapDispatchToProps) (Foo);
//client.js, link up saga, redux, and React Component
const render = App => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
combinedReducers,
initialState,
composeEnhancers(applyMiddleware(sagaMiddleware))
);
store.runSaga(rootSaga);
return ReactDOM.hydrate(
<ReduxProvider store={store}>
<BrowserRouter><AppContainer><App/></AppContainer></BrowserRouter>
</ReduxProvider>,
document.getElementById('root')
);
}